#-*- coding:utf-8 -*-

import os
import ctypes
import traceback
import logging
import logging.handlers
import time

from DLLib import *
from BDLib import *

class BD3Client:
    def __init__(self, server, bdid):
        self.server = server
        self.id = bdid
        self.name = ''
        self.bdlog = self._getBDLogger()
        self.bin = ctypes.create_string_buffer(4096)
        self.tx = []
        self.tlast = 0
        self.dst = 0
        self.rx = [[None for _ in range(8)] for _ in range(4)]
        self.mrx = [[[None for _ in range(8)] for _ in range(2048)] for _ in range(4)]
        self.ml = [[0 for _ in range(2048)] for _ in range(4)]
        self.mf = [[False for _ in range(2048)] for _ in range(4)]


    def _getBDLogger(self):
        logger = logging.getLogger(str(self.id))

        handler = logging.handlers.TimedRotatingFileHandler(os.path.join('BDLog', str(self.id) + '.log'), when='MIDNIGHT', interval=1, backupCount=self.server._logdays)
        handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s'))
        logger.addHandler(handler)
        logger.setLevel(self.server._loglevel)

        return logger


    def _getTerLogger(self):
        logger = logging.getLogger(self.name)

        handler = logging.handlers.TimedRotatingFileHandler(os.path.join('DataLog', self.name + '.log'), when='MIDNIGHT', interval=1, backupCount=self.server._logdays)
        handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s'))
        logger.addHandler(handler)
        logger.setLevel(self.server._loglevel)

        return logger


    def InPush(self, d, s):
        for i in range(0, len(d)):
            self.bin[s] = d[i]
            s += 1
        return s


    def BD3DataHandle(self, d, urid):
        """
        处理BD3协议数据，根据数据包类型进行解析并执行相应操作。

        参数:
            d (bytes): 接收到的原始数据字节流。
            urid (int): 用户远程ID，用于标识发送方，若为None则使用self.id。

        返回:
            int: 已处理的数据长度。
        """

        # 处理以0x68或0xA5开头的数据包（下行链路数据）
        if d[0] == 0x68 or d[0] == 0xA5:
            # 解析数据包并获取有效长度和设备名称
            l, name = self.server.pfDLP(d, self.id)
            # 将解析后的数据发送到主发送队列
            self.server.mst_send.put(("$%d,%s" % (urid if urid else self.id, d[:l].hex().upper())).encode())

            # 如果设备名称发生变化，则更新名称和日志记录器
            if self.name != name:
                self.name = name
                self.terlog = self._getTerLogger()
            # 根据日志空间设置格式化日志内容
            if self.server._logspace:
                slog = ' '.join(['[DL<]'] + ["%02X" % i for i in d[:l]])
            else:
                slog = '[DL<] ' + d[:l].hex().upper()
            # 记录警告级别日志并写入DL发送队列
            self.terlog.warning(slog)
            self.server.dl_send.put(slog + '\n')

            # 插入数据库记录并更新设备名称
            self.server.dbInsert(self.id)
            self.server.dbUpName(self.id, name)
            return l
        
        # 处理以0x24开头的数据包（上行链路响应数据）
        if d[0] == 0x24:
            r = d[1:]
            l = 1
            # 循环解析多个子消息
            while len(r):
                # 提取消息ID
                id = r[1] * 256 + r[0]
                if not id:
                    l += 2
                    break
                msg = "$%d,24%04X" % (self.id, id)
                # 根据不同消息ID处理具体业务逻辑
                if id == 0x2000:
                    # 版本信息处理
                    ver = "%d.%d" % (r[4] * 256 + r[3], r[6] * 256 + r[5])
                    self.server.mst_send.put(("%s,%s" % (msg, ver)).encode())
                elif id == 0x2001:
                    # 位置信息处理
                    loc1 = int.from_bytes(r[3:7], byteorder = 'little', signed = True)
                    loc2 = int.from_bytes(r[7:11], byteorder = 'little', signed = True)
                    self.server.mst_send.put(("%s,%d.%d" % (msg, loc1, loc2)).encode())
                elif id == 0x2002:
                    # BSI信息处理
                    bsi = ','.join(["%02X" % i for i in r[3:3+r[2]]])
                    self.server.mst_send.put(("%s,%s" % (msg, bsi)).encode())
                elif id == 0x2004:
                    # 空操作响应
                    self.server.mst_send.put(msg.encode())
                elif id == 0x2005:
                    # 单字节参数响应
                    self.server.mst_send.put(("%s,%d" % (msg, r[3])).encode())

                # 更新偏移量并继续处理下一个子消息
                il = r[2] + 3
                l += il
                r = r[il:]

            # 插入数据库记录
            self.server.dbInsert(self.id)
            # 如果所有必要变量都存在，则更新设备信息
            if 'loc1' in locals() and 'loc2' in locals() and 'ver' in locals() and 'bsi' in locals():
                self.server.dbUpInfo(self.id, loc1, loc2, ver, bsi)
            # 发送原始数据到主发送队列
            self.server.mst_send.put(("$%d,%s" % (self.id, d[:l].hex().upper())).encode())
            return l

        # 对于未知类型的数据包，直接发送原始数据
        self.server.mst_send.put(("$%d,%s" % (self.id, d.hex().upper())).encode())
        return len(d)
    
    def BD3ReportSend(self, lid):
        """
        发送BD3报告数据
        
        参数:
            lid: 要发送的列表数据，包含需要传输的标识符
            
        功能:
            构造并发送BD3协议格式的报告数据包
        """
        # 构造数据包头部
        d = [0x08, 0x25, 0x04]
        d.append(len(lid))
        # 将lid中的每个元素按小端序格式添加到数据包中
        for i in lid:
            d.append(i & 0xFF)
            d.append(i >> 8)
        # 将数据包转换为十六进制字符串格式
        s = ''.join([f"{i:02X}" for i in d])
        # 通过服务器发送构造好的数据包
        self.server.bdtx.put(f"${self.id},8C00060{len(d)-1:02X}{s}")
        # 记录发送时间
        self.dst = int(time.time())

    def BD3isRecved(self, fid, pri):
        """
        检查指定fid和优先级的数据是否已接收
        
        参数:
            fid: 功能标识符
            pri: 优先级
            
        返回值:
            bool: 如果在指定优先级和fid下有任何数据被接收则返回True，否则返回False
        """
        # 检查8个可能的接收位置是否存在数据
        for i in range(8):
            if self.mrx[pri][fid][i] is not None:
                return True
        return False
    
    def BD3FidValid(self, fid):
        """
        计算指定fid之前的有效fid列表
        
        参数:
            fid: 当前功能标识符
            
        返回值:
            list: 包含有效fid的列表，范围从(fid-512)到fid之间的所有值
        """
        l = []
        s = fid - 512
        # 处理fid范围跨越0点的情况（循环缓冲区逻辑）
        if s < 0:
            s += 2048
            # 先添加从s到2048-1的fid
            for i in range(s, 2048):
                l.append(i)
            # 再添加从0到fid-1的fid
            for i in range(fid):
                l.append(i)
        else:
            # 正常情况，直接添加从s到fid-1的fid
            for i in range(s, fid):
                l.append(i)
        return l
    
    def BD3FidInvalid(self, fid):
        """
        计算指定fid之后的无效fid列表
        
        参数:
            fid: 当前功能标识符
            
        返回值:
            list: 包含无效fid的列表，即从(fid-512)开始的有效fid列表
        """
        # 计算起始位置
        i = fid - 512
        # 处理循环缓冲区边界情况
        if i < 0:
            i += 2048
        # 复用BD3FidValid方法计算结果
        return self.BD3FidValid(i)
    
    def BD3RxNew(self, d):
        """
        处理接收到的BD3协议数据，解析数据包并根据协议规则进行处理。

        参数:
            d (bytes): 接收到的原始数据字节流。

        返回值:
            无返回值。
        """
        lid = []
        # 循环处理输入数据，直到数据为空
        while (len(d) > 0):
            # 解析数据包头部信息
            valid = True if d[1]&0x80 == 0x80 else False  # 判断数据包是否有效
            pri = (d[1]&0x60) >> 5                        # 提取优先级字段
            fid = d[0] + (d[1]&0x07) * 256                # 提取功能ID
            il = d[2] + 4                                 # 计算数据包长度
            da = d[4:il]                                  # 提取数据内容
            h = d[3] & 0x0F                               # 提取头部控制字段
            d = d[il:]                                    # 剩余未处理数据

            # 根据数据包有效性进行不同处理
            if not valid:
                # 旧协议处理逻辑（当前被注释）
                # if da[0] == 0x25 and da[1] == 4:
                #     self.BD3Resend(da[2], da[3:])
                # else:
                    self.BD3TXR(pri, h, da)
            else:
                # 新协议处理逻辑
                id = h & 0x07  # 提取消息ID
                self.mrx[pri][fid][id] = da  # 存储接收到的数据片段
                if h >= 8:
                    self.ml[pri][fid] = id+1  # 更新最大消息ID
                # 如果所有片段都已接收，则组合处理
                if self.ml[pri][fid]:
                    tl = 0
                    for i in range(self.ml[pri][fid]):
                        if self.mrx[pri][fid][i] is None:
                            break
                        tl = self.InPush(self.mrx[pri][fid][i], tl)
                    else:
                        self.BD3MsgHandle(tl)  # 处理完整消息
                        self.mf[pri][fid] = True  # 标记消息已处理

        # 如果服务器未设置最大发送数量，则直接返回
        if not self.server._dsmax:
            return

        # 清理无效的功能ID相关状态
        l = self.BD3FidInvalid(fid)
        for i in l:
            self.mrx[pri][i] = [None for _ in range(8)]
            self.ml[pri][i] = 0
            self.mf[pri][i] = False

        # 检查是否需要上报未确认的消息
        if time.time() - self.dst > self.server._dsspan:
            l = self.BD3FidValid(fid)
            sid = None
            # 遍历有效功能ID，查找需要上报的消息
            for i in l:
                if self.BD3isRecved(i, pri) and sid is None:
                    sid = i
                if not self.mf[pri][i] and sid:
                    lid.append((pri<<13)+i)
                    if (len(lid) >= self.server._dsmax):
                        break
            # 如果有待上报的消息，则执行发送操作
            if len(lid):
                self.BD3ReportSend(lid)

    def BD3MsgHandle(self, tl):
        """
        处理BD3协议消息的主函数
        
        参数:
            tl (int): 消息总长度
            
        返回值:
            无返回值
            
        功能说明:
            该函数负责解析和处理BD3协议格式的数据包，包括网关转发和数据解压缩等操作
        """
        urid = 0
        # 检查消息头是否为0x26，如果是则提取URID并更新数据偏移
        if self.bin.raw[0] == 0x26:
            urid = int.from_bytes(self.bin.raw[1:5], byteorder = 'big', signed = False)
            self.bin.raw = self.bin.raw[5:]
            tl -= 5

        # 循环处理剩余的数据包
        while tl > 0:
            # 处理网关IP相关消息，类型为0xEB
            if hasattr(self.server, '_gwip') and self.bin.raw[0] == 0xEB:
                il = self.bin.raw[1] * 256 + self.bin.raw[2] + 6
                t = "$%d," % self.id + ''.join(["%02X" % i for i in self.bin.raw[:il]]) + '\r\n'
                self.server.GwSend(2, t.encode())
            # 处理压缩数据消息，类型为0x88
            elif self.bin.raw[0] == 0x88:
                il = self.bin.raw[2] * 256 + self.bin.raw[3] + 5
                l = self.server.zip.DeData(self.server.bout, self.bin, il)
                if l < 0:
                    raise Exception('zip.DeData < 0')
                d = self.server.bout.raw[:l]
                # 循环处理解压后的数据
                while l > 0:
                    ll = self.BD3DataHandle(d, urid)
                    d = d[ll:]
                    l -= ll
            # 处理普通数据消息
            else:
                d = self.bin.raw[:tl]
                il = self.BD3DataHandle(d, urid)

            # 更新数据缓冲区和剩余长度
            self.bin.raw = self.bin.raw[il:]
            tl -= il


    def BD3TXR(self, pri, h, d):
        """
        处理BD3协议的传输接收功能
        
        参数:
            pri: 优先级标识符，用于索引rx数组的第一维
            h: 高度或偏移量参数，决定数据处理方式
            d: 要处理的数据值
            
        返回值:
            无返回值
        """
        # 处理h小于8的情况，直接将数据存入rx数组
        if h < 8:
            self.rx[pri][h] = d
            return
        
        # 处理h等于8的情况，调用InPush方法处理数据
        if h == 8:
            tl = self.InPush(d, 0)
        else:
            # 处理h大于8的情况，先存储数据，然后顺序处理前面的所有数据
            id = h - 8
            self.rx[pri][id] = d
            tl = 0
            for i in range(0, id + 1):
                tl = self.InPush(self.rx[pri][i], tl)

        # 调用消息处理函数处理最终结果
        self.BD3MsgHandle(tl)


    def BD2TXR(self, d):
        """
        处理接收到的BD2协议数据，并根据协议类型进行解析和响应。

        参数:
            d (bytes): 接收到的原始数据字节流，包含协议头和数据内容。

        返回值:
            无返回值。该函数主要完成数据解析、数据库更新、日志记录和消息发送等操作。
        """

        # 处理协议头为 0x06 的情况
        if d[0] == 0x06:
            # 处理子类型为 0x24 的情况，解析位置、版本和BSI信息
            if d[1] == 0x24:
                # 解析经纬度信息
                loc1 = int.from_bytes(d[10:14], byteorder='little', signed=True)
                loc2 = int.from_bytes(d[14:18], byteorder='little', signed=True)
                # 解析版本信息
                ver = "%d.%d" % (d[20], d[22]) if len(d) > 23 else ''
                # 解析BSI信息
                bsi = d[26:].decode(errors='ignore') if len(d) > 26 else ''
                # 插入设备信息到数据库
                self.server.dbInsert(self.id)
                # 更新设备信息
                self.server.dbUpInfo(self.id, loc1, loc2, ver, bsi)
                # 发送响应消息
                self.server.mst_send.put(("$%07d,0624,%d.%d,%s,%s" % (self.id, loc1, loc2, ver, bsi)).encode())
            else:
                # 处理其他子类型，直接发送原始数据
                self.server.mst_send.put(("$%07d,06%02X,%s" % (self.id, d[1], d[2:].decode(errors='ignore'))).encode())
            return

        # 处理协议头为 0x08 的情况，调用InPush处理数据
        if d[0] == 0x08:
            self.InPush(d[2:], 0)
            return

        # 处理协议头为 0x00 的情况，调用InPush处理数据并指定偏移量
        if d[0] == 0x00:
            self.InPush(d[2:], d[1] * (BD2_SIZE - 2))
            return

        # 处理其他协议头的情况，解析urid并调用InPush处理数据
        urid = 0
        if d[0] == 0x0C:
            l = self.InPush(d[1:], 0)
        elif d[0] == 0x04:
            l = self.InPush(d[2:], d[1] * (BD2_SIZE - 2))
        elif d[0] == 0x0F:
            # 解析urid
            urid = int.from_bytes(d[1:5], byteorder='big', signed=False)
            l = self.InPush(d[5:], 0)
        else:
            raise Exception('header')

        # 判断是否需要解压缩数据
        if self.bin.raw[0] == 0x88:
            # 解压缩数据
            l = self.server.zip.DeData(self.server.bout, self.bin, self.bin.raw[2] * 256 + self.bin.raw[3] + 5)
            if l < 0:
                raise Exception('zip.DeData < 0')
            d = self.server.bout.raw[:l]
        else:
            # 直接使用原始数据
            d = self.bin.raw[:l]

        # 处理解析后的数据，获取文件名并发送响应
        l, name = self.server.pfDLP(d, self.id)
        self.server.mst_send.put(("$%07d,%s" % (urid if urid else self.id, d[:l].hex().upper())).encode())

        # 更新设备名称和日志记录器
        if self.name != name:
            self.name = name
            self.terlog = self._getTerLogger()
        # 记录日志
        if self.server._logspace:
            slog = ' '.join(['[DL<]'] + ["%02X" % i for i in d])
        else:
            slog = '[DL<] ' + d.hex().upper()
        self.terlog.warning(slog)
        self.server.dl_send.put(slog + '\n')

        # 更新数据库中的设备名称
        self.server.dbInsert(self.id)
        self.server.dbUpName(self.id, name)
